/*!
 * \file      gpio-board.h
 *
 * \brief     Target board GPIO driver implementation
 *
 * \copyright Revised BSD License, see section \ref LICENSE.
 *
 * \code
 *                ______                              _
 *               / _____)             _              | |
 *              ( (____  _____ ____ _| |_ _____  ____| |__
 *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 *               _____) ) ____| | | || |_| ____( (___| | | |
 *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
 *              (C)2013-2017 Semtech
 *
 * \endcode
 *
 * \author    Miguel Luis ( Semtech )
 *
 * \author    Gregory Cristian ( Semtech )
 */
#include <stddef.h>
#include "n32l43x_gpio.h"
#include "n32l43x_rcc.h"
#include "gpio-board.h"

static Gpio_t *GpioIrq[16];

/*!
 * \brief Initializes the given GPIO object
 *
 * \param [IN] obj    Pointer to the GPIO object
 * \param [IN] pin    Pin name ( please look in pinName-board.h file )
 * \param [IN] mode   Pin mode [PIN_INPUT, PIN_OUTPUT,
 *                              PIN_ALTERNATE_FCT, PIN_ANALOGIC]
 * \param [IN] config Pin config [PIN_PUSH_PULL, PIN_OPEN_DRAIN]
 * \param [IN] type   Pin type [PIN_NO_PULL, PIN_PULL_UP, PIN_PULL_DOWN]
 * \param [IN] value  Default output value at initialization
 */
void GpioMcuInit( Gpio_t *obj, PinNames pin, PinModes mode, PinConfigs config, PinTypes type, uint32_t value )
{
    if(pin < IOE_0) {
        GPIO_InitType GPIO_InitStructure;
        obj->pin = pin;
        if(pin == NC) {
            return ;
        }
        obj->pinIndex = (0x1 << (obj->pin & 0xF));
        if((obj->pin & 0xF0) == 0x00) {
            obj->port = GPIOA;
            RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_AFIO, ENABLE);
        } else if((obj->pin & 0xF0) == 0x10) {
            obj->port = GPIOB;
            RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB | RCC_APB2_PERIPH_AFIO, ENABLE);
        } else if((obj->pin & 0xF0) == 0x20) {
            obj->port = GPIOC;
            RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOC | RCC_APB2_PERIPH_AFIO, ENABLE);
        } else if((obj->pin & 0xF0) == 0x30) {
            obj->port = GPIOD;
            RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOD | RCC_APB2_PERIPH_AFIO, ENABLE);
        } else {
            assert_param(LMN_STATUS_ERROR);
        }
        obj->pull = type;
        GPIO_InitStructure.Pin = obj->pinIndex;
        GPIO_InitStructure.GPIO_Pull = (GPIO_PuPdType)type;
        GPIO_InitStructure.GPIO_Current = GPIO_DC_2mA;
        if(mode == PIN_INPUT) {
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Input;
        } else if(mode == PIN_ANALOGIC) {
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Analog;
        } else if(mode == PIN_ALTERNATE_FCT) {
            if(config == PIN_OPEN_DRAIN) {
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
            } else {
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
            }
            GPIO_InitStructure.GPIO_Alternate = value;
        } else { //mode output
            if(config == PIN_OPEN_DRAIN) {
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
            } else {
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
            }
        }
        if(mode == PIN_OUTPUT) {
            GpioMcuWrite(obj, value);
        }
        GPIO_InitPeripheral(obj->port, &GPIO_InitStructure);
    } else {
        #if defined(BOARD_IOE_EXT)
        GpioIoeInit( obj, pin, mode, config, type, value );
        #endif
    }
}

/*!
 * \brief Sets a user defined object pointer
 *
 * \param [IN] context User defined data object pointer to pass back
 *                     on IRQ handler callback
 */
void GpioMcuSetContext( Gpio_t *obj, void* context )
{
    obj->Context = context;
}

/*!
 * \brief GPIO IRQ Initialization
 *
 * \param [IN] obj         Pointer to the GPIO object
 * \param [IN] irqMode     IRQ mode [NO_IRQ, IRQ_RISING_EDGE,
 *                                   IRQ_FALLING_EDGE, IRQ_RISING_FALLING_EDGE]
 * \param [IN] irqPriority IRQ priority [IRQ_VERY_LOW_PRIORITY, IRQ_LOW_PRIORITY
 *                                       IRQ_MEDIUM_PRIORITY, IRQ_HIGH_PRIORITY
 *                                       IRQ_VERY_HIGH_PRIORITY]
 * \param [IN] irqHandler  Callback function pointer
 */
void GpioMcuSetInterrupt( Gpio_t *obj, IrqModes irqMode, IrqPriorities irqPriority, GpioIrqHandler *irqHandler )
{
    if(obj->pin < IOE_0) {
        uint32_t priority = 0;
        IRQn_Type IRQnb = EXTI0_IRQn;
        GPIO_InitType GPIO_InitStructure;
        
        if(irqHandler == NULL) {
            return ;
        }
        
        obj->IrqHandler = irqHandler;
        
        GPIO_InitStructure.Pin = obj->pinIndex;
        
        if(irqMode == IRQ_RISING_EDGE) {
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IT_Rising;
        } else if(irqMode == IRQ_FALLING_EDGE) {
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IT_Falling;
        } else {
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IT_Rising_Falling;
        }
        
        GPIO_InitStructure.GPIO_Pull = (GPIO_PuPdType)obj->pull;
        GPIO_InitStructure.GPIO_Slew_Rate = GPIO_Slew_Rate_High;
        GPIO_InitPeripheral(obj->port, &GPIO_InitStructure);
        /*Configure key EXTI Line to key input Pin*/
        GPIO_ConfigEXTILine(obj->pin >> 4, obj->pin&0xF);
        switch(irqPriority) {
            case IRQ_VERY_LOW_PRIORITY:
            case IRQ_LOW_PRIORITY:
                priority = 3;
            break;
            case IRQ_MEDIUM_PRIORITY:
                priority = 2;
            break;
            case IRQ_HIGH_PRIORITY:
                priority = 1;
            break;
            case IRQ_VERY_HIGH_PRIORITY:
            default:
                priority = 0;
            break;
        }
        switch(obj->pinIndex) {
            case GPIO_PIN_0:
                IRQnb = EXTI0_IRQn;
                break;
            case GPIO_PIN_1:
                IRQnb = EXTI1_IRQn;
                break;
            case GPIO_PIN_2:
                IRQnb = EXTI2_IRQn;
                break;
            case GPIO_PIN_3:
                IRQnb = EXTI3_IRQn;
                break;
            case GPIO_PIN_4:
                IRQnb = EXTI4_IRQn;
                break;
            case GPIO_PIN_5:
            case GPIO_PIN_6:
            case GPIO_PIN_7:
            case GPIO_PIN_8:
            case GPIO_PIN_9:
                IRQnb = EXTI9_5_IRQn;
                break;
            case GPIO_PIN_15:
            case GPIO_PIN_14:
            case GPIO_PIN_13:
            case GPIO_PIN_12:
            case GPIO_PIN_11:
            case GPIO_PIN_10:
                IRQnb = EXTI15_10_IRQn;
                break;
            default:
                break;
        }
        GpioIrq[(obj->pin) & 0xF] = obj;
        NVIC_SetPriority(IRQnb, priority);
        NVIC_EnableIRQ(IRQnb);
    } else {
        #if defined(BOARD_IOE_EXT)
        GpioIoeSetInterrupt( obj, irqMode, irqPriority, irqHandler );
        #endif
    }
}

/*!
 * \brief Removes the interrupt from the object
 *
 * \param [IN] obj Pointer to the GPIO object
 */
void GpioMcuRemoveInterrupt( Gpio_t *obj )
{
    if( obj->pin < IOE_0 )
    {
        // Clear callback before changing pin mode
        GpioIrq[( obj->pin ) & 0x0F] = NULL;

        GPIO_InitType GPIO_InitStructure;

        GPIO_InitStructure.Pin =  obj->pinIndex ;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Analog;
        GPIO_InitPeripheral( obj->port, &GPIO_InitStructure );
    }
    else
    {
#if defined( BOARD_IOE_EXT )
        // IOExt Pin
        GpioIoeRemoveInterrupt( obj );
#endif
    }
}

/*!
 * \brief Writes the given value to the GPIO output
 *
 * \param [IN] obj   Pointer to the GPIO object
 * \param [IN] value New GPIO output value
 */
void GpioMcuWrite( Gpio_t *obj, uint32_t value )
{
    if( obj->pin < IOE_0 )
    {
        if( obj == NULL )
        {
            assert_param( LMN_STATUS_ERROR );
        }
        // Check if pin is not connected
        if( obj->pin == NC )
        {
            return;
        }
        GPIO_WriteBit( obj->port, obj->pinIndex , (Bit_OperateType)value );
    }
    else
    {
#if defined( BOARD_IOE_EXT )
        // IOExt Pin
        GpioIoeWrite( obj, value );
#endif
    }
}

/*!
 * \brief Toggle the value to the GPIO output
 *
 * \param [IN] obj   Pointer to the GPIO object
 */
void GpioMcuToggle( Gpio_t *obj )
{
    if( obj->pin < IOE_0 )
    {
        if( obj == NULL )
        {
            assert_param( LMN_STATUS_ERROR );
        }

        // Check if pin is not connected
        if( obj->pin == NC )
        {
            return;
        }
        GPIO_WriteBit( obj->port, obj->pinIndex, (Bit_OperateType)!GPIO_ReadInputDataBit(obj->port, obj->pinIndex));
    }
    else
    {
#if defined( BOARD_IOE_EXT )
        // IOExt Pin
        GpioIoeToggle( obj );
#endif
    }
}

/*!
 * \brief Reads the current GPIO input value
 *
 * \param [IN] obj Pointer to the GPIO object
 * \retval value   Current GPIO input value
 */
uint32_t GpioMcuRead( Gpio_t *obj )
{
    if( obj->pin < IOE_0 )
    {
        if( obj == NULL )
        {
            assert_param( LMN_STATUS_ERROR );
        }
        // Check if pin is not connected
        if( obj->pin == NC )
        {
            return 0;
        }
        return GPIO_ReadInputDataBit( obj->port, obj->pinIndex );
    }
    else
    {
#if defined( BOARD_IOE_EXT )
        // IOExt Pin
        return GpioIoeRead( obj );
#else
        return 0;
#endif
    }
}
void GPIO_EXTI_Callback(uint16_t gpioPin)
{
    uint8_t callbackIndex = 0;

    if( gpioPin > 0 )
    {
        while( gpioPin != 0x01 )
        {
            gpioPin = gpioPin >> 1;
            callbackIndex++;
        }
    }

    if( ( GpioIrq[callbackIndex] != NULL ) && ( GpioIrq[callbackIndex]->IrqHandler != NULL ) )
    {
        GpioIrq[callbackIndex]->IrqHandler( GpioIrq[callbackIndex]->Context );
    }
}
void EXTI0_IRQHandler()
{
    EXTI_ClrITPendBit(EXTI_LINE0);
    GPIO_EXTI_Callback(GPIO_PIN_0);
}
void EXTI1_IRQHandler()
{
    EXTI_ClrITPendBit(EXTI_LINE1);
    GPIO_EXTI_Callback(GPIO_PIN_1);
}
void EXTI2_IRQHandler()
{
    EXTI_ClrITPendBit(EXTI_LINE2);
    GPIO_EXTI_Callback(GPIO_PIN_2);
}
void EXTI3_IRQHandler()
{
    EXTI_ClrITPendBit(EXTI_LINE3);
    GPIO_EXTI_Callback(GPIO_PIN_3);
}
void EXTI4_IRQHandler()
{
    EXTI_ClrITPendBit(EXTI_LINE4);
    GPIO_EXTI_Callback(GPIO_PIN_4);
}
void EXTI9_5_IRQHandler()
{
    if(EXTI_GetITStatus(EXTI_LINE5) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE5);
        GPIO_EXTI_Callback(GPIO_PIN_5);
    }
    if(EXTI_GetITStatus(EXTI_LINE6) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE6);
        GPIO_EXTI_Callback(GPIO_PIN_6);
    }
    if(EXTI_GetITStatus(EXTI_LINE7) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE7);
        GPIO_EXTI_Callback(GPIO_PIN_7);
    }
    if(EXTI_GetITStatus(EXTI_LINE8) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE8);
        GPIO_EXTI_Callback(GPIO_PIN_8);
    }
    if(EXTI_GetITStatus(EXTI_LINE9) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE9);
        GPIO_EXTI_Callback(GPIO_PIN_9);
    }
}
void EXTI15_10_IRQHandler()
{
    if(EXTI_GetITStatus(EXTI_LINE10) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE10);
        GPIO_EXTI_Callback(GPIO_PIN_10);
    }
    if(EXTI_GetITStatus(EXTI_LINE11) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE11);
        GPIO_EXTI_Callback(GPIO_PIN_11);
    }
    if(EXTI_GetITStatus(EXTI_LINE12) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE12);
        GPIO_EXTI_Callback(GPIO_PIN_12);
    }
    if(EXTI_GetITStatus(EXTI_LINE13) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE13);
        GPIO_EXTI_Callback(GPIO_PIN_13);
    }
    if(EXTI_GetITStatus(EXTI_LINE14) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE14);
        GPIO_EXTI_Callback(GPIO_PIN_14);
    }
    if(EXTI_GetITStatus(EXTI_LINE15) == SET) {
        EXTI_ClrITPendBit(EXTI_LINE15);
        GPIO_EXTI_Callback(GPIO_PIN_15);
    }
}

